﻿
'/*=====================================================================
'模 块 名：DataLink

'描    述：封装了串口和网口通讯的数据连接类

'使用方法：《DataLink类库使用说明.txt》文件

'依赖模块：Microsoft .NET Framework 3.5及以上

'原始版本：1.02
'作    者：刘德亮
'完成日期：2019-11-25

'修改版本：1.03
'修改作者：刘德亮
'修改日期：2021-3-18
'修改记录：1.OpenLink()函数入口检查参数正确性，不同种类时检查参数个数的正确性
'          2.MsgBox()完善提示信息
'          3.使用方法单独在《DataLink类库使用说明.txt》文件中
'          4.添加CheckIP()用于检查IP地址的正确性
'          5.Thread_TCP_Listen()改100ms检查一次连接请求
'          6.Dns.GetHostByName()方法过时新版本不再支持，改为Dns.GetHostEntry()
'=====================================================================*/
Imports System.IO.Ports
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading

Namespace FL_8x0.Common



    Public Class DataLink

        '错误号
        Public Const LINK_OK As UInt16 = &H0
        Public Const LINK_ERR1 As UInt16 = &H1 '参数错误
        Public Const LINK_ERR2 As UInt16 = &H2 '通讯接口打开错误
        Public Const LINK_ERR3 As UInt16 = &H3 '通讯接口关闭错误
        Public Const LINK_ERR4 As UInt16 = &H4 '网线未连接
        Public Const LINK_ERR5 As UInt16 = &H5 '网络目标不能连接
        Public Const LINK_ERR As UInt16 = &HFFFF  '其它错误

        '串口连接
        Private uart As SerialPort           '串口句柄

        '网口连接
        Private socketListen As TcpListener  'tcp-server句柄
        Private socketTCP As TcpClient       'tcp-client句柄
        Private socketUDP As UdpClient       'UDP句柄

        Private tdThread As Thread           'tcp-server监听线程句柄

        '当前参数
        Private strLinkConfig As String     '当前连接配置字符串
        Private strLinkType As String       '当前连接类型
        Private IsLinking As Boolean        '当前连接中



        '=====================================================================
        '函 数 名:New
        '参    数:
        '返 回 值:
        '功能描述:构造函数
        '=====================================================================
        Public Sub New()
            uart = Nothing

            socketListen = Nothing
            socketTCP = Nothing
            socketUDP = Nothing

            tdThread = Nothing  '启动线程时再申请空间

            strLinkConfig = ""
            strLinkType = ""
            IsLinking = False
        End Sub



        '=====================================================================
        '函 数 名:Finalize
        '参    数:
        '返 回 值:
        '功能描述:析构函数
        '=====================================================================
        Protected Overrides Sub Finalize()
            If IsLinking = True Then  '连接已经打开
                Try
                    '未正常关闭的通讯接口，有一次机会断开连接
                    If strLinkType = "uart" Then
                        If Not IsNothing(uart) Then
                            If uart.IsOpen Then
                                uart.Close()   '断开串口
                            End If
                        End If

                    ElseIf strLinkType = "tcp-server" Then
                        If Not IsNothing(tdThread) Then
                            tdThread.Abort()  '终止线程

                            If tdThread.ThreadState <> ThreadState.Unstarted Then
                                tdThread.Join()  '等待完全终止
                            End If
                        End If

                        If Not IsNothing(socketListen) Then
                            socketListen.Stop()  '停止tcp监听
                        End If

                        If Not IsNothing(socketTCP) Then
                            socketTCP.Close()       '断开网络连接
                        End If

                    ElseIf strLinkType = "tcp-client" Then
                        If Not IsNothing(socketTCP) Then
                            socketTCP.Close()       '断开网络连接
                        End If

                    ElseIf strLinkType = "udp" Then
                        If Not IsNothing(socketUDP) Then
                            socketUDP.Close()       '断开网络连接
                        End If

                    End If
                Catch ex As Exception
                    MsgBox(ex.Message, vbOKOnly, ex.Source)
                End Try
            End If

            strLinkConfig = ""
            strLinkType = ""
            IsLinking = False

            MyBase.Finalize()
        End Sub



        '=====================================================================
        '函 数 名:IsLinkOpening
        '参    数:
        '返 回 值:True = 已经打开
        '功能描述:检查当前数据连接是否已经打开
        '=====================================================================
        Public Function IsLinkOpening() As Boolean
            Return IsLinking
        End Function



        '=====================================================================
        '函 数 名:GetLinkType
        '参    数:
        '返 回 值:"uart"、"tcp-server"、"tcp-client"、"udp"
        '功能描述:读取当前接口类型
        '=====================================================================
        Public Function GetLinkType() As String
            Return strLinkType
        End Function



        '=====================================================================
        '函 数 名:CheckIP
        '参    数:strIP = IP字符串(192.168.0.1)
        '返 回 值:True=正确，False=错误
        '功能描述:检查IP地址的正确性
        '=====================================================================
        Private Function CheckIP(strIP As String) As Boolean
            Dim str1 As String() = Split(strIP.ToLower, ".")  '分解参数

            If str1.Length <> 4 Then
                Return False '参数错误(缺少.分隔)
            End If

            Dim i As Byte

            For i = 0 To 3
                If Len(str1(i)) > 3 Then
                    Return False '参数错误(位数过多)
                End If

                If Not IsNumeric(str1(i)) Then
                    Return False '参数错误(非数字)
                End If

                If CInt(str1(i)) < 0 Or CInt(str1(i)) > 255 Then
                    Return False '参数错误(超出0-255)
                End If
            Next

            Return True '正确
        End Function



        '=====================================================================
        '函 数 名:OpenLink
        '参    数:strConfig = 连接配置字符串(不区分大小写)
        '         "uart,COM[串口号],[波特率],[数据位],[校验位],[停止位]"，举例："uart,COM1,9600,8,N,1"
        '         "tcp-server,[local port]"，举例："tcp-server,10000"
        '         "tcp-client,[remote ip],[remote port]"，举例："tcp-client,192.168.10.52,10000"
        '         "udp,[remote ip],[remote port]"，举例："udp,192.168.10.52,10000"
        '
        '返 回 值:LINK_OK, 
        '         LINK_ERR*
        '功能描述:打开数据连接
        '=====================================================================
        Public Function OpenLink(ByVal strConfig As String) As UInt16
            If IsLinking = True Then  '已经打开
                Return LINK_ERR2 '通讯接口打开错误
            End If

            If InStr(strConfig, ",") <= 0 Then
                Return LINK_ERR1 '参数错误
            End If

            strLinkConfig = strConfig

            Try
                Dim str1 As String() = Split(strConfig.ToLower, ",")  '分解参数

                If str1.Length > 0 Then
                    '去前后空格
                    Dim i As Int16
                    For i = 0 To str1.Length - 1
                        str1(i) = Trim(str1(i))
                    Next

                    strLinkType = str1(0)   '硬件接口类型

                    If strLinkType = "uart" Then  '"uart,COM1,9600,8,N,1"
                        If str1.Length <> 6 Then
                            Return LINK_ERR1 '参数错误
                        End If

                        uart = New SerialPort

                        '串口号
                        If InStr(str1(1).ToUpper, "COM") > 0 Then
                            uart.PortName = str1(1)
                        Else
                            Return LINK_ERR1 '参数错误
                        End If

                        '波特率
                        If IsNumeric(str1(2)) Then
                            If CLng(str1(2)) >= 300 Or CLng(str1(2)) <= 115200 Then
                                uart.BaudRate = CInt(str1(2))
                            Else
                                Return LINK_ERR1 '参数错误
                            End If
                        Else
                            Return LINK_ERR1 '参数错误
                        End If

                        '数据位
                        If IsNumeric(str1(3)) Then
                            If CLng(str1(3)) >= 7 Or CLng(str1(3)) <= 8 Then
                                uart.DataBits = CInt(str1(3))
                            Else
                                Return LINK_ERR1 '参数错误
                            End If
                        Else
                            Return LINK_ERR1 '参数错误
                        End If

                        '校验位
                        If InStr(str1(4).ToUpper, "N") > 0 Then
                            uart.Parity = Parity.None
                        ElseIf InStr(str1(4).ToUpper, "O") > 0 Then
                            uart.Parity = Parity.Odd
                        ElseIf InStr(str1(4).ToUpper, "E") > 0 Then
                            uart.Parity = Parity.Even
                        Else
                            Return LINK_ERR1 '参数错误
                        End If

                        '停止位
                        If IsNumeric(str1(5)) Then
                            If CLng(str1(5)) >= 1 Or CLng(str1(5)) <= 2 Then
                                uart.StopBits = CInt(str1(5))
                            Else
                                Return LINK_ERR1 '参数错误
                            End If
                        Else
                            Return LINK_ERR1 '参数错误
                        End If

                        uart.ReadBufferSize = 4096
                        uart.Open()

                    ElseIf strLinkType = "tcp-server" Then  '"tcp-server,[local port]"
                        If str1.Length <> 2 Then
                            Return LINK_ERR1 '参数错误
                        End If

                        'local port
                        If Not IsNumeric(str1(1)) Then
                            Return LINK_ERR1 '参数错误
                        Else
                            If CLng(str1(1)) < 0 Or CLng(str1(1)) > 65535 Then
                                Return LINK_ERR1 '参数错误
                            End If
                        End If

                        If My.Computer.Network.IsAvailable Then '本地已连接网络
                            Dim strLocalName As String = Dns.GetHostName  '获得本地计算机的主机名
                            Dim Localips() As IPAddress = Dns.GetHostEntry(strLocalName).AddressList '获得本地计算机的IP地址

                            If Localips.Count = 0 Then
                                Return LINK_ERR4 '网线未连接
                            End If

                            socketListen = New TcpListener(Localips(0), CInt(str1(1)))
                            socketListen.Start()    '启动监听

                            tdThread = New Thread(AddressOf Thread_TCP_Listen)
                            tdThread.Start()

                        Else
                            Return LINK_ERR4 '网线未连接
                        End If

                    ElseIf strLinkType = "tcp-client" Then  '"tcp-client,[remote ip],[remote port]"
                        If str1.Length <> 3 Then
                            Return LINK_ERR1 '参数错误
                        End If

                        'remote ip
                        If Not CheckIP(str1(1)) Then '检查IP地址的正确性
                            Return LINK_ERR1 '参数错误
                        End If

                        'remote port
                        If Not IsNumeric(str1(2)) Then
                            Return LINK_ERR1 '参数错误
                        Else
                            If CLng(str1(2)) < 0 Or CLng(str1(2)) > 65535 Then
                                Return LINK_ERR1 '参数错误
                            End If
                        End If

                        If My.Computer.Network.IsAvailable Then '本地已连接网络
                            Dim siteResponds = My.Computer.Network.Ping(str1(1))  'PING远程计算机

                            If siteResponds = True Then
                                socketTCP = New TcpClient
                                socketTCP.Connect(str1(1), CInt(str1(2)))

                            Else
                                Return LINK_ERR5 '网络目标不能连接
                            End If

                        Else
                            Return LINK_ERR4 '网线未连接
                        End If

                    ElseIf strLinkType = "udp" Then  '"udp,[local port]、[remote ip],[remote port]"
                        If str1.Length <> 4 Then
                            Return LINK_ERR1 '参数错误
                        End If

                        'local port
                        If Not IsNumeric(str1(1)) Then
                            Return LINK_ERR1 '参数错误
                        Else
                            If CLng(str1(1)) < 0 Or CLng(str1(1)) > 65535 Then
                                Return LINK_ERR1 '参数错误
                            End If
                        End If

                        'remote ip
                        If Not CheckIP(str1(2)) Then '检查IP地址的正确性
                            Return LINK_ERR1 '参数错误
                        End If

                        'remote port
                        If Not IsNumeric(str1(3)) Then
                            Return LINK_ERR1 '参数错误
                        Else
                            If CLng(str1(3)) < 0 Or CLng(str1(3)) > 65535 Then
                                Return LINK_ERR1 '参数错误
                            End If
                        End If

                        If My.Computer.Network.IsAvailable Then '本地已连接网络
                            Dim siteResponds = My.Computer.Network.Ping(str1(2))  'PING远程计算机

                            If siteResponds = True Then
                                If CInt(Str(1)) = 0 Then
                                    socketUDP = New UdpClient
                                Else
                                    socketUDP = New UdpClient(CInt(str1(1)))
                                End If
                                socketUDP.Connect(str1(2), CInt(str1(3)))

                            Else
                                Return LINK_ERR5 '网络目标不能连接
                            End If
                        Else
                            Return LINK_ERR4 '网线未连接
                        End If

                    Else
                        Return LINK_ERR1 '参数错误
                    End If

                Else
                    Return LINK_ERR1 '参数错误
                End If

            Catch ex As Exception
                MsgBox(ex.Message, vbOKOnly, ex.Source)
                Return LINK_ERR2 '通讯接口打开错误
            End Try

            IsLinking = True

            Return LINK_OK
        End Function



        '=====================================================================
        '函 数 名:Thread_TCP_Listen
        '参    数:
        '返 回 值:
        '功能描述:TCP server 侦听连接处理线程(100ms 定时阻塞)
        '=====================================================================
        Private Sub Thread_TCP_Listen()
            While True
                '网关连接监听
                If socketListen.Pending() = True Then   '有未接受的连接请求
                    '关闭已经产生的连接(即只接受1个TCP Client连接)
                    If Not IsNothing(socketTCP) Then
                        socketTCP.Close()       '断开网络连接
                        socketTCP = Nothing
                    End If

                    socketTCP = socketListen.AcceptTcpClient '新建用于答复的TcpClient
                End If

                '让给其它线程运行
                System.Threading.Thread.Sleep(100)  '100ms 
            End While
        End Sub




        '=====================================================================
        '函 数 名:CloseLink
        '参    数:
        '返 回 值:LINK_OK, 
        '         LINK_ERR*
        '功能描述:关闭数据连接
        '=====================================================================
        Public Function CloseLink() As UInt16
            If IsLinking = False Then   '未打开
                Return LINK_ERR3 '通讯接口关闭错误
            End If

            If strLinkType = "uart" Then
                If Not IsNothing(uart) Then
                    If uart.IsOpen Then
                        uart.Close()   '断开串口
                        uart = Nothing
                    End If
                End If

            ElseIf strLinkType = "tcp-server" Then
                '关闭线程
                If Not IsNothing(tdThread) Then
                    tdThread.Abort()  '终止线程

                    If tdThread.ThreadState <> ThreadState.Unstarted Then
                        tdThread.Join()  '等待完全终止
                    End If

                    tdThread = Nothing  '启动线程时再申请空间
                End If

                If Not IsNothing(socketListen) Then
                    socketListen.Stop()     '停止tcp监听
                    socketListen = Nothing
                End If

                If Not IsNothing(socketTCP) Then
                    socketTCP.Close()       '断开网络连接
                    socketTCP = Nothing
                End If

            ElseIf strLinkType = "tcp-client" Then
                If Not IsNothing(socketTCP) Then
                    socketTCP.Close()       '断开网络连接
                    socketTCP = Nothing
                End If

            ElseIf strLinkType = "udp" Then
                If Not IsNothing(socketUDP) Then
                    socketUDP.Close()       '断开网络连接
                    socketTCP = Nothing
                End If

            Else
                Return LINK_ERR3 '通讯接口关闭错误
            End If

            strLinkConfig = ""
            strLinkType = ""
            IsLinking = False

            Return LINK_OK
        End Function



        '=====================================================================
        '函 数 名:ReadData
        '参    数:dat = 字节数组(空间必须足够)
        '         num = 需要接收字节数
        '返 回 值:实际接收字节数(连接为打开或错误时返回0)
        '功能描述:接收数据
        '=====================================================================
        Public Function ReadData(ByRef dat As Byte(), ByVal num As UInt16) As UInt16
            If IsLinking = False Then   '未打开
                Return 0
            End If

            Dim len As UInt16 = 0

            Try
                If strLinkType = "uart" Then
                    If Not IsNothing(uart) Then
                        If uart.IsOpen Then
                            If uart.BytesToRead > 0 Then
                                len = uart.Read(dat, 0, num)
                            End If
                        End If
                    End If

                ElseIf strLinkType = "tcp-server" Then
                    If Not IsNothing(socketTCP) Then
                        If socketTCP.Available > 0 Then
                            Dim sf As SocketFlags = SocketFlags.None
                            len = socketTCP.Client.Receive(dat, num, sf)
                        End If
                    End If

                ElseIf strLinkType = "tcp-client" Then
                    If Not IsNothing(socketTCP) Then
                        If socketTCP.Available > 0 Then
                            Dim sf As SocketFlags = SocketFlags.None
                            len = socketTCP.Client.Receive(dat, num, sf)
                        End If
                    End If

                ElseIf strLinkType = "udp" Then
                    If Not IsNothing(socketUDP) Then
                        If socketUDP.Available > 0 Then
                            Dim sf As SocketFlags = SocketFlags.None
                            len = socketUDP.Client.Receive(dat, num, sf)
                        End If
                    End If

                End If

            Catch ex As Exception
                MsgBox(ex.Message, vbOKOnly, ex.Source)
            End Try

            Return len
        End Function



        '=====================================================================
        '函 数 名:WriteData
        '参    数:dat = 字节数组
        '         num = 需要发送字节数
        '返 回 值:实际发送字节数(连接为打开或错误时返回0)
        '功能描述:发送数据
        '=====================================================================
        Public Function WriteData(ByRef dat As Byte(), ByVal num As UInt16) As UInt16
            If IsLinking = False Then   '未打开
                Return 0
            End If

            Dim len As UInt16 = 0

            Try
                If strLinkType = "uart" Then
                    If Not IsNothing(uart) Then
                        If uart.IsOpen Then
                            uart.Write(dat, 0, num)
                            len = num
                        End If
                    End If

                ElseIf strLinkType = "tcp-server" Then
                    If Not IsNothing(socketTCP) Then
                        If socketTCP.Connected Then
                            Dim sf As SocketFlags = SocketFlags.None
                            len = socketTCP.Client.Send(dat, num, sf)
                        End If
                    End If

                ElseIf strLinkType = "tcp-client" Then
                    If Not IsNothing(socketTCP) Then
                        If socketTCP.Connected Then
                            Dim sf As SocketFlags = SocketFlags.None
                            len = socketTCP.Client.Send(dat, num, sf)
                        End If
                    End If

                ElseIf strLinkType = "udp" Then
                    If Not IsNothing(socketUDP) Then
                        Dim sf As SocketFlags = SocketFlags.None
                        len = socketUDP.Client.Send(dat, num, sf)
                    End If

                End If

            Catch ex As Exception
                MsgBox(ex.Message, vbOKOnly, ex.Source)
            End Try

            Return len
        End Function



        '=====================================================================
        '函 数 名:Available
        '参    数
        '返 回 值:字节数(连接为打开或错误时返回0)
        '功能描述:检查接收有效数据
        '=====================================================================
        Public Function Available() As UInt16
            If IsLinking = False Then   '未打开
                Return 0
            End If

            Dim len As UInt16 = 0

            Try
                If strLinkType = "uart" Then
                    If Not IsNothing(uart) Then
                        If uart.IsOpen Then
                            len = uart.BytesToRead
                        End If
                    End If

                ElseIf strLinkType = "tcp-server" Then
                    If Not IsNothing(socketTCP) Then
                        len = socketTCP.Available
                    End If

                ElseIf strLinkType = "tcp-client" Then
                    If Not IsNothing(socketTCP) Then
                        len = socketTCP.Available
                    End If

                ElseIf strLinkType = "udp" Then
                    If Not IsNothing(socketUDP) Then
                        len = socketUDP.Available
                    End If

                End If

            Catch ex As Exception
                MsgBox(ex.Message, vbOKOnly, ex.Source)
            End Try

            Return len
        End Function


    End Class
End Namespace